<?php

class Request
{
	/**
	 * Scheme for http
	 * @var string
	 */
	const SCHEME_HTTP = 'http';

	/**
	 * Scheme for https
	 * @var string
	 */
	const SCHEME_HTTPS = 'https';

	/**
	 * max loop forward
	 * @var int
	 */
	const MAX_LOOP_FORWARD = 4;

	/**
	 * PATH_INFO
	 * @var string
	 */
	protected $_pathInfo = '';

	/**
	 * REQUEST_URI
	 * @var string;
	 */
	protected $_requestUri;

	/**
	 * @var bool
	 */
	protected $_dispatched = 0;

	/**
	 * @var array
	 */
	protected $_params = array();

	/**
	 * get/post
	 * @var string
	 */
	protected $_method = 'GET';

	/**
	 * @var string
	 */
	protected $_controllerName = null;

	/**
	 * @var string
	 */
	protected $_actionName = 'index';

	/**
	 * @var Controller
	 */
	protected $_controller = null;

	/**
	 * constructor
	 * @return void
	 */
	public function __construct($params = null, $actionId = null)
	{
		if (null != $params)
		{
			$this -> setParams($params);
		}

		if (null != $actionId)
		{
			$this -> setActionId($actionId);
		}
	}

	/**
	 * Retrieve a member of the $_COOKIE superglobal
	 *
	 * If no $key is passed, returns the entire $_COOKIE array.
	 *
	 * @todo How to retrieve from nested arrays
	 * @param string $key
	 * @param mixed $default Default value to use if key not found
	 * @return mixed Returns null if key does not exist
	 */
	static public function getCookie($key = null, $default = null)
	{
		if (null === $key)
		{
			return $_COOKIE;
		}

		return (isset($_COOKIE[$key])) ? $_COOKIE[$key] : $default;
	}

	/**
	 * Retrieve a member of the $_SERVER superglobal
	 *
	 * If no $key is passed, returns the entire $_SERVER array.
	 *
	 * @param string $key
	 * @param mixed $default Default value to use if key not found
	 * @return mixed Returns null if key does not exist
	 */
	static public function getServer($key = null, $default = null)
	{
		if (null === $key)
		{
			return $_SERVER;
		}

		return (isset($_SERVER[$key])) ? $_SERVER[$key] : $default;
	}

	/**
	 * Retrieve a member of the $_ENV superglobal
	 *
	 * If no $key is passed, returns the entire $_ENV array.
	 *
	 * @param string $key
	 * @param mixed $default Default value to use if key not found
	 * @return mixed Returns null if key does not exist
	 */
	static public function getEnv($key = null, $default = null)
	{
		if (null === $key)
		{
			return $_ENV;
		}

		return (isset($_ENV[$key])) ? $_ENV[$key] : $default;
	}

	/**
	 * Return the value of the given HTTP header. Pass the header name as the
	 * plain, HTTP-specified header name. Ex.: Ask for 'Accept' to get the
	 * Accept header, 'Accept-Encoding' to get the Accept-Encoding header.
	 *
	 * @param string $header HTTP header name
	 * @return string|false HTTP header value, or false if not found
	 * @throws Exception
	 */
	static public function getHeader($header)
	{

		// Try to get it from the $_SERVER array first
		$temp = 'HTTP_' . strtoupper(str_replace('-', '_', $header));
		if (isset($_SERVER[$temp]))
		{
			return $_SERVER[$temp];
		}

		// This seems to be the only way to get the Authorization header on
		// Apache
		if (function_exists('apache_request_headers'))
		{
			$headers = apache_request_headers();
			if (isset($headers[$header]))
			{
				return $headers[$header];
			}
			$header = strtolower($header);
			foreach ($headers as $key => $value)
			{
				if (strtolower($key) == $header)
				{
					return $value;
				}
			}
		}

		return false;
	}

	/**
	 * Get the request URI scheme
	 *
	 * @return string
	 */
	static public function getScheme()
	{
		return (self::getServer('HTTPS') == 'on') ? self::SCHEME_HTTPS : self::SCHEME_HTTP;
	}

	/**
	 * Get the HTTP host.
	 *
	 * "Host" ":" host [ ":" port ] ; Section 3.2.2
	 * Note the HTTP Host header is not the same as the URI host.
	 * It includes the port while the URI host doesn't.
	 *
	 * @return string
	 */
	static public function getHttpHost()
	{
		$host = self::getServer('HTTP_HOST');

		if (!empty($host))
		{
			return $host;
		}

		$scheme = self::getScheme();
		$name = self::getServer('SERVER_NAME');
		$port = self::getServer('SERVER_PORT');

		if (null === $name)
		{
			return '';
		}
		elseif (($scheme == self::SCHEME_HTTP && $port == 80) || ($scheme == self::SCHEME_HTTPS && $port == 443))
		{
			return $name;
		}
		else
		{
			return $name . ':' . $port;
		}
	}

	/**
	 * Get the client's IP addres
	 *
	 * @param  boolean $checkProxy
	 * @return string
	 */
	public function getClientIp($checkProxy = true)
	{
		$ip = '127.0.0.1';

		if ($checkProxy && self::getServer('HTTP_CLIENT_IP') != null)
		{
			$ip = self::getServer('HTTP_CLIENT_IP');
		}
		else
		if ($checkProxy && self::getServer('HTTP_X_FORWARDED_FOR') != null)
		{
			$ip = self::getServer('HTTP_X_FORWARDED_FOR');
		}
		else
		{
			$ip = self::getServer('REMOTE_ADDR');
		}
		return $ip;
	}

	/**
	 * Is https secure request
	 *
	 * @return boolean
	 */
	public function isSecure()
	{
		return (self::getScheme() === self::SCHEME_HTTPS);
	}

	/**
	 * Is this a Flash request?
	 *
	 * @return boolean
	 */
	public function isFlashRequest()
	{
		return (strstr(strtolower(slef::getHeader('USER_AGENT')), ' flash')) ? true : false;
	}

	/**
	 * Is the request a Javascript XMLHttpRequest?
	 *
	 * Should work with Prototype/Script.aculo.us, possibly others.
	 *
	 * @return boolean
	 */
	public function isAjaxRequest()
	{
		return (self::getHeader('X_REQUESTED_WITH') == 'XMLHttpRequest');
	}

	/**
	 * Was the request made by OPTIONS?
	 *
	 * @return boolean
	 */
	public function isOptions()
	{
		return 'OPTIONS' == $this -> getMethod();
	}

	/**
	 * Was the request made by HEAD?
	 *
	 * @return boolean
	 */
	public function isHead()
	{
		return 'HEAD' == $this -> getMethod();
	}

	/**
	 * Was the request made by DELETE?
	 *
	 * @return boolean
	 */
	public function isDelete()
	{
		return 'DELETE' == $this -> getMethod();
	}

	/**
	 * Was the request made by POST?
	 *
	 * @return boolean
	 */
	public function isPost()
	{
		return 'POST' == $this -> getMethod();
	}

	/**
	 * Was the request made by GET?
	 *
	 * @return boolean
	 */
	public function isGet()
	{
		return 'GET' == $this -> getMethod();
	}

	/**
	 * Was the request made by PUT?
	 *
	 * @return boolean
	 */
	public function isPut()
	{
		return 'PUT' == $this -> getMethod();
	}

	/**
	 * set is dispatched
	 * @return bool
	 */
	public function setDispatched($flag)
	{
		$this -> _dispatched = (bool)$flag;
		return $this;
	}

	/**
	 * is dispatched
	 * @return bool
	 */
	public function isDispatched()
	{
		return $this -> _dispatched;
	}

	/**
	 * @return Controller
	 */
	public function getController()
	{
		return $this -> _controller;
	}

	/**
	 * @param Controller $controller
	 * @return Request
	 */
	public function setController(Controller $controller)
	{
		$this -> _controller = $controller;
		return $this;
	}

	/**
	 * get controller name
	 * @return string
	 */
	public function getControllerName()
	{
		return $this -> _controllerName;
	}

	/**
	 * @param string $controllerName
	 * @return Request
	 */
	public function setControllerName($controllerName)
	{
		$this -> _controllerName = strtolower($controllerName);
		return $this;
	}

	/**
	 * get action name
	 * @return string
	 */
	public function getActionName()
	{
		return $this -> _actionName;
	}

	/**
	 * set action name
	 * @param string $actionName
	 * @return Request
	 */
	public function setActionName($actionName)
	{
		$this -> _actionName = strtolower($actionName);
		return $this;
	}

	/**
	 * get method name
	 * @return string
	 */
	public function getMethod()
	{
		return $this -> _method;
	}

	/**
	 * set method name
	 * @param string $method
	 * @return Request
	 */
	public function setMethod($method)
	{
		$this -> _method = $method;
		return $this;
	}

	/**
	 * @return string
	 */
	public function getActionId()
	{
		return $this -> _controllerName . ':' . $this -> _actionName;
	}

	/**
	 * @param string $actionId
	 * @return Request
	 */
	public function setActionId($actionId)
	{
		$arr = explode(':', $actionId);
		$this -> setControllerName($arr[0]);
		$this -> setActionName(isset($arr[1]) ? $arr[1] : 'index');
		return $this;
	}

	/**
	 * @param string $key
	 * @param any $value
	 * @return any
	 */
	public function getParam($key, $value = null)
	{
		if (method_exists($this, $method = 'get' . ucfirst($key)))
		{
			return $this -> {$method}();
		}
		return isset($this -> _params[$key]) ? $this -> _params[$key] : $value;
	}

	/**
	 * @param string $key
	 * @param any $value
	 * @return Request
	 */
	public function setParam($key, $value)
	{
		if (method_exists($this, $method = 'set' . ucfirst($key)))
		{
			return $this -> {$method}($value);
		}
		else
		{
			$this -> _params[$key] = $value;
		}
		return $this;
	}

	/**
	 * @return Request
	 */
	public function getParams()
	{
		return $this -> _params;
	}

	/**
	 * set params
	 * @param array $params
	 * @param Request
	 */
	public function setParams($params)
	{
		if (!empty($params))
		{
			foreach ($params as $key => $value)
			{
				$this -> setParam($key, $value);
			}
		}
		return $this;

	}

	/**
	 * get all params
	 * @return array
	 */
	public function getAllParams()
	{
		return $this -> _params;
	}

	/**
	 * @param string $actionId
	 * @return bool
	 */
	public function isDispatchable($actionId = null, $class = null)
	{
		if (null == $class)
		{
			$arr = explode(':', $actionId);
			$class = implode('_', array_map('ucfirst', explode('.', str_replace('@', '.controller.', $arr[0]))));
		}

		if (class_exists($class, 0))
		{
			return 1;

		}

		if (file_exists($file = NUO_MODULE_PATH . '/' . strtolower(str_replace('_', '/', $class)) . '.php'))
		{
			require_once $file;
			if (class_exists($class, 0))
			{
				return 1;
			}
		}

		return 0;
	}

	/**
	 * dispatch
	 * @return string
	 */
	public function dispatch($params = null, $actionId = null)
	{
		if (null != $params)
		{
			$this -> setParams($params);
		}

		if (null != $actionId)
		{
			$this -> setActionId($actionId);
		}

		$maxLoop = self::MAX_LOOP_FORWARD;

		ob_start();

		$user = Nuo::getViewer();

		do
		{
			/**
			 * @var string
			 */
			$class = implode('_', array_map('ucfirst', explode('.', str_replace('@', '.controller.', $this -> _controllerName))));

			if (!$this -> isDispatchable(null, $class))
			{
				throw new Exception(sprintf("controller %s is not dispatchable!", $this -> getActionId()));
			}

			/**
			 * @var Controller
			 */
			$this -> _controller = new $class($this);

			/**
			 * dispatch an action.
			 */
			$this -> _controller -> dispatch($this -> _actionName);

		}
		while( --$maxLoop >0 && !$this->_dispatched);

		$result = ob_get_clean();

		$format = strtolower($this -> getParam('format', 'html'));

		if ('json' == $format)
		{

			return json_encode($this -> _controller -> data);
		}

		if ('jsonp' == $format)
		{
			$callback = $this -> getParam('callback', 'callback');

			return sprintf("%s(%s);", $callback, json_encode($this -> _controller -> data));

		}
		return $this -> _controller -> render() . $result;
	}

	/**
	 * get base url
	 * @return string
	 */
	static public function getBaseUrl()
	{
		return Nuo::getBaseUrl();
	}

	/**
	 * get base href
	 * @return string
	 */
	static public function getBaseHref()
	{
		return Nuo::getBaseHref();
	}

	/**
	 * execute a request
	 * @param array $params
	 * @param string $actionId
	 * @return string
	 */
	static public function execute($params, $actionId)
	{
		$request = new self($params, $actionId);
		return $request -> dispatch();
	}

}
